﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading;

namespace TuringMachineSimulator
{
    public delegate void TuringTransitionCompletedHandler(object sender, EventArgs e);

    /// <summary>
    /// This class simulate the turing machine
    /// </summary>
    public class TuringSimulator
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public TuringSimulator()
        { 
        }

        /// <summary>
        /// Event which get fired once one transaction is completed
        /// </summary>
        public event TuringTransitionCompletedHandler TuringTransitionCompleted;

        /// <summary>
        /// Simulate the turing machine
        /// </summary>
        /// <param name="tmloader">Turing machine program</param>
        /// <param name="tm">Turing machine</param>
        /// <param name="delay">delay in milli-seconds between two transition</param>
        /// <param name="surface">Graphics context on which turing machine need to be drawn</param>
        /// <returns>Summary of running turing machine on program</returns>
        private string Simulate(TuringProgramLoader tmloader, TuringMachine tm, int delay, Graphics surface)
        {
            int iteration = 0;

            int i_state=0;
            char input;
            int n_state;
            char output;
            char move;
            bool state_not_found = false;
            DrawTape(tm, surface,tm.get_current_state(),tm.get_symbol());
            Thread.Sleep(delay);

            while ((iteration < 100) && !tm.is_accepted() && !tm.Tape_overflow && !state_not_found)
            {

                input = tm.get_symbol();
                if (tmloader.findTransition(tm.get_current_state(), input, out n_state, out output, out move))
                {
                    i_state = tm.get_current_state();
                    tm.set_current_state(n_state);
                    tm.set_symbol(output);
                    if (move == 'l')
                        tm.move_left();
                    else
                        tm.move_right();

                }
                else
                {
                    state_not_found = true;
                }


                iteration++;
                DrawTape(tm, surface,i_state, input,n_state,output,move);
                if(TuringTransitionCompleted!=null)
                TuringTransitionCompleted(this, new EventArgs());
                Thread.Sleep(delay);

                //getch();



            }
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("");
            sb.AppendLine("********* STATISTICS **********");
            sb.AppendLine("Total Iterations performed are " + iteration);
            sb.AppendLine("current state is " + tm.get_current_state());

            if (iteration >= 100)
                sb.AppendLine("Iterations are infinite");
            if (tm.is_accepted())
                sb.AppendLine("Input String is accepted");
            if (tm.Tape_overflow)
                sb.AppendLine("Execution Tape Overflow");
            if (state_not_found)
                sb.AppendLine("State not found in Transition table :" + tm.get_current_state());
            //textTape.AppendText(sb.ToString());
            //MessageBox.Show("Done");
            return sb.ToString();
        }


        /// <summary>
        /// Draw the current state of turing machine tape
        /// </summary>
        /// <param name="tm">Turing Machine which is running</param>
        /// <param name="surface">Graphics context on which turing machine need to be drawn</param>

        private void DrawTape(TuringMachine tm, Graphics surface)
        {

            Font drawFont = new Font("Arial", 10);
            SolidBrush solidbr = new SolidBrush(Color.Black);
            int index = 0;
            //surface.Clear(Color.DimGray);
            surface.Clear(Color.Khaki);
            foreach (char ch in tm.getTuringMachineTape)
            {
                if (index == tm.get_index())
                {
                    solidbr.Color = Color.Red;
                    surface.DrawLine(new Pen(solidbr), new Point(10 * index, 20), new Point(10 * index, 120));
                    StringFormat strformat = new StringFormat();
                    strformat.FormatFlags = StringFormatFlags.DirectionVertical;
                    //surface.DrawString("Current state: " + tm.get_current_state().ToString(), drawFont, solidbr, new Point(10 * index, 40), strformat);
                    surface.DrawString("Turing Machine", drawFont, new SolidBrush(Color.Black), new Point(10 * (index - 2), 40), strformat);
                    surface.DrawString(string.Format("Current state: {0}", tm.get_current_state()), drawFont, solidbr, new Point(10 * (index + 1), 40));
                    surface.DrawString(string.Format("Current input: {0}", tm.get_symbol()), drawFont, solidbr, new Point(10 * (index + 1), 55));
                    
                    surface.DrawRectangle(new Pen(new SolidBrush(Color.Navy)), new Rectangle(10 * (index - 2), 40, 200, 100));
                }
                else if (ch == '1')
                    solidbr.Color = Color.Black;
                else if (ch != 'b')
                    solidbr.Color = Color.Blue;
                else
                    solidbr.Color = Color.White;

                surface.DrawString(ch.ToString(), drawFont, solidbr, 10 * index, 10);
                index++;
            }
        }

        /// <summary>
        /// Draw the current state of turing machine tape
        /// </summary>
        /// <param name="tm">Turing Machine which is running</param>
        /// <param name="surface">Graphics context on which turing machine need to be drawn</param>
        /// <param name="input">Current input</param>
        /// <param name="n_state">New State</param>
        /// <param name="output">Output symbol</param>
        /// <param name="move">Move (l-left, r-right)</param>
        private void DrawTape(TuringMachine tm, Graphics surface,int i_state, char input, int n_state=0, char output='0', char move='0')
        {          
           
            Font drawFont = new Font("Arial", 10);
            SolidBrush solidbr = new SolidBrush(Color.Black);
            int index = 0;
            //surface.Clear(Color.DimGray);
            surface.Clear(Color.Khaki );
            foreach (char ch in tm.getTuringMachineTape)
            {
                if (index == tm.get_index())
                {
                    solidbr.Color = Color.Red;
                    surface.DrawLine(new Pen(solidbr), new Point(10 * index, 20), new Point(10 * index, 120));
                    StringFormat strformat = new StringFormat();
                    strformat.FormatFlags = StringFormatFlags.DirectionVertical;
                   // surface.DrawString("Current state: " + tm.get_current_state().ToString(), drawFont, solidbr, new Point(10 * index, 40), strformat);
                    surface.DrawString("Turing Machine", drawFont, new SolidBrush(Color.Black), new Point(10 * (index - 2), 40), strformat);
                    surface.DrawString(string.Format("Input state: {0}", i_state), drawFont, solidbr, new Point(10 * (index + 1), 55));
                    surface.DrawString(string.Format("Input: {0}", input), drawFont, solidbr, new Point(10 * (index + 1), 70));
                    if (move != '0')
                    {
                        surface.DrawString(string.Format("({0},{1},{2},{3},{4})", i_state, input, n_state, output, move), drawFont, solidbr, new Point(10 * (index + 1), 40));
                        surface.DrawString(string.Format("Output state: {0}", n_state), drawFont, solidbr, new Point(10 * (index + 1), 85));
                        surface.DrawString(string.Format("Output symbol: {0}", output), drawFont, solidbr, new Point(10 * (index + 1), 100));
                        surface.DrawString(string.Format("Move: {0}", move), drawFont, solidbr, new Point(10 * (index + 1), 115));
                    }
                    
                    surface.DrawRectangle(new Pen(new SolidBrush(Color.Navy)), new Rectangle(10 * (index - 2), 40, 200, 100));
                   
                }
                else if (ch == '1')
                    solidbr.Color = Color.Black ;
                else if (ch != 'b')
                    solidbr.Color = Color.Blue ;
                else
                    solidbr.Color = Color.White;

                surface.DrawString(ch.ToString(), drawFont, solidbr, 10 * index, 10);
                index++;
            }
        }

        /// <summary>
        /// Run turing machine program on a given input
        /// </summary>
        /// <param name="fileName">path of the file containing turing machine program</param>
        /// <param name="input">input to turing machine. e.g. 111b111</param>
        /// <param name="surface">Graphics object on which to draw turing machine</param>
        /// <param name="delay">delay in milli-second between two transaction</param>
        /// <returns></returns>
        public string Run(string fileName, string input, Graphics surface, int delay)
        {
            //Load the program
            TuringProgramLoader tmloader = new TuringProgramLoader();
            tmloader.readTransitions(fileName);

            //Initialize the turing machine and load the input data to turing machine's tape
            TuringMachine tm = new TuringMachine();
            tm.setData(input);

            //Run the simulator
            return Simulate(tmloader, tm, delay, surface);
        }

    }
}
